/*
=======================================================================

  Sniper - BOTS
  
=======================================================================
*/
#include "g_local.h"

#include "bots_game2cgame.h"

#include "bg_local.h"
#include "bots_local.h"
#include "bots_sniper.h"
#include "bots_bodyguard.h"


#define BOTS_DOT	1


// Update the bitfield
void bots_updateClientData_sniper(gentity_t *self, bots_g2cg_sniper_t *p) {
	if (!self->client)
		return;

	BOTS_SETBIT(p->bits, self->lasersight,				BOTS_G2CG_SNIPER_SIGHT);
	BOTS_SETBIT(p->bits, self->client->invisible,		BOTS_G2CG_SNIPER_CLOAKED);
	BOTS_SETBIT(p->bits, self->client->qb_cloakWalk,	BOTS_G2CG_SNIPER_CLOAKWALK);
}

// Ad hoc function as that state is not preserved anywhere. Argh.
void bots_updateCloaking(gentity_t *self, short sTime) {
	BOTS_G2CG_GETCLASSDATA(bots_g2cg_sniper_t,self)->sCloakTime = sTime;
}




void BOTS_explode_flare(gentity_t* self, vec3_t origin)
{
	gentity_t* player = NULL;

	// set the position
	G_SetOrigin(self, origin);
	VectorCopy (self->s.pos.trBase, self->s.origin);
	self->s.eType = ET_BOTS_FLARE;
	trap_LinkEntity( self );

	// decloak snipers that are within the light
	while ((player = findradiusCLIENT(player, self->s.pos.trBase, 200)) != NULL)
	{
		if (!player->client)
			continue;
		if (self->parent == player)
			continue;

		if (player->bots_class == BOTS_SNIPER)
		{
			if (player->client->invisible)
			{
				player->client->last_movement = level.time;
				BOTS_sniper_cloak(player->s.clientNum, qfalse);
			}
		}
	}

}

void BOTS_fire_flare (int clientNum)
{
	gentity_t*	ent = g_entities + clientNum;
	gentity_t*	flare;
	vec3_t		forward;
	vec3_t		muzzle;

	if (!ent->client)
		return;

	// check to see if this class can use flares
	if (ent->bots_class != BOTS_SNIPER)
	{
		BOTS_cp_console( clientNum, "Only the Sniper fires flares.");
		return;
	}

	// check ammo
	if (ent->client->ps.ammo[WP_BFG] < 20)
	{
		BOTS_cp_console( clientNum, "You need at least 20 cells for a flare.");
		return;
	}
	ent->client->ps.ammo[WP_BFG] -= 20;

	// set aiming directions
	AngleVectors (ent->client->ps.viewangles, forward, NULL, NULL);

	// setup muzzle point
	CalcMuzzlePoint(ent, forward, NULL, NULL, muzzle);

	VectorNormalize (forward);

	flare = G_Spawn();
	flare->classname = "flare";
	flare->nextthink = level.time + 10000;
	flare->think = G_FreeEntity;
	flare->s.eType = ET_MISSILE;
	flare->r.svFlags = SVF_USE_CURRENT_ORIGIN;

	// the railgun is not used in the cgame missle routine so we can
	// key off of it to do what we want.  We could also use the machine
	// gun or shotgun.
	flare->s.weapon = WP_RAILGUN;
	flare->r.ownerNum = ent->s.number;
	flare->s.clientNum = ent->s.clientNum;
	flare->parent = ent;
	flare->damage = 5;
	flare->splashDamage = 0;
	flare->splashRadius = 1;
	flare->methodOfDeath = MOD_PLASMA;
	flare->clipmask = MASK_SHOT;

	flare->s.pos.trType = TR_LINEAR;
	flare->s.pos.trTime = level.time - 50; // move a bit on the very first frame
	VectorCopy( muzzle, flare->s.pos.trBase );
	VectorScale( forward, 2000, flare->s.pos.trDelta );
	SnapVector( flare->s.pos.trDelta );			// save net bandwidth

	VectorCopy (muzzle, flare->r.currentOrigin);
}	

/////////////////////////////////////////////
//
// BOTS_sight_think
//
// This function is the think routine for the laser sight.  It will
// calculate the position for the entity each time it is called.
//
void BOTS_sight_think(gentity_t* self)
{
	gentity_t*	ent;
	vec3_t		wallp;
	vec3_t		forward;
	vec3_t		muzzle;
	trace_t		tr;

	if (!self->parent)
		return;

	if (!self->parent->client)
		return;

	trap_UnlinkEntity(self);

	ent = self->parent;

	// set aiming directions
	AngleVectors (ent->client->ps.viewangles, forward, NULL, NULL);

	//void CalcMuzzlePoint ( gentity_t *ent, vec3_t forward, vec3_t right, vec3_t up, vec3_t muzzlePoint ) {
	// setup muzzle point
	CalcMuzzlePoint(ent, forward, NULL, NULL, muzzle);

	// Setup end point of the vector we want to trace
	VectorCopy(muzzle, wallp);
	VectorMA(wallp, 8192, forward, wallp);

	// do the trace
	trap_Trace(&tr, muzzle, NULL, NULL, wallp, ent->s.number, MASK_SHOT);

	// Line complete ? (ie. no collision)
	if (tr.fraction == 1.0)
		return;

	// save net bandwidth
	SnapVector( tr.endpos );

	// set the new position
	G_SetOrigin(self, tr.endpos);

	// make the entity appear before the wall
	VectorCopy(tr.plane.normal, forward);
	VectorNormalize(forward);
	VectorScale(forward, BOTS_DOT, forward);

	VectorCopy (self->s.pos.trBase, self->s.origin);
	VectorAdd(self->s.origin, forward, wallp);
	VectorCopy (wallp, self->s.origin2);

	trap_LinkEntity (self);

	self->nextthink = level.time + 10;
}

/////////////////////////////////////////////
//
// BOTS_sight
//
// Toggles the laser sight dot
//
void BOTS_sight(int clientNum)
{
	gentity_t* ent = g_entities + clientNum;
	
	gentity_t*	sight;

	if (!ent->client)
		return;

	// check to see if this class can use sight
	if (ent->bots_class != BOTS_SNIPER)
	{
		BOTS_cp_console( clientNum, "Only the Sniper can use sight.");
		return;
	}

	// sight is on - free the entity and return
	if (ent->lasersight)
	{
		G_FreeEntity(ent->lasersight);
		ent->lasersight = NULL;
		ent->client->i_sightTime = 0;
		return;
	}

	//
	// create entity for sight
	//
	sight					= G_Spawn();
	sight->s.eType			= ET_BOTS_SIGHT;
	sight->s.modelindex		= 1;
	sight->s.modelindex2	= 0; // This is non-zero if it's a dropped item
	sight->s.number			= sight - g_entities;
	sight->s.frame			= 1;
	sight->classname		= "sight";
	sight->parent			= ent;
	sight->think			= BOTS_sight_think;
	sight->nextthink		= level.time + FRAMETIME;
	sight->s.clientNum		= clientNum;

	trap_LinkEntity (sight);

	ent->lasersight = sight;

	// sight must be on for 2 seconds to do full damage
	ent->client->i_sightTime = level.time + 2000;

	// write client data to the config string
	//csfix: BOTS_SniperConfigString(ent->s.clientNum);
}


/////////////////////////////////////////////
//
// BOTS_alarm_think
//
// Think routine for alarms.  This function will determine if any entities
// near it qualify for an alarm message to be sent.
//
void BOTS_alarm_think(gentity_t* self)
{
	gentity_t*	player = NULL;
	qboolean	qb_hasflag = qfalse;
	char		class_name[20];
	char		alarmname[30];
	char		message[500];
	char		stats[500];
	int			j = 0;

	stats[0] = 0;

	if (!self->parent)
		return;

	if (!self->parent->client)
		return;

	while ((player = findradiusCLIENT(player, self->s.pos.trBase, 300)) != NULL)
	{
		if (!player->client)
			continue;
		if (player->client->sess.sessionTeam == TEAM_SPECTATOR)
			continue;
		if (player->health <= 0)
			continue;
		if (!visible(self, player))
			continue;	// alarm can not see the player
		if (OnSameTeam (self->parent, player))
			continue;
		if (self->parent == player)	// needed for non team play - testing
			continue;
		if (player->bots_class == BOTS_INFILTRATOR)
			continue;

		switch(player->bots_class)
		{
		case BOTS_CAPTAIN:
			strcpy(class_name, CLASS1);
			break;
		case BOTS_BODYGUARD:
			strcpy(class_name, CLASS2);
			break;
		case BOTS_SNIPER:
			strcpy(class_name, CLASS3);
			break;
		case BOTS_SOLDIER:
			strcpy(class_name, CLASS4);
			break;
		case BOTS_BERZERKER:
			strcpy(class_name, CLASS5);
			break;
		case BOTS_INFILTRATOR:
			strcpy(class_name, CLASS6);
			break;
		case BOTS_KAMIKAZEE:
			strcpy(class_name, CLASS7);
			break;
		case BOTS_NURSE:
			strcpy(class_name, CLASS8);
			break;
		case BOTS_SCIENTIST:
			strcpy(class_name, CLASS9);
			break;
		default:
			strcpy(class_name, "ERROR");
			break;
		}

		Com_sprintf(stats, sizeof(stats), "%s %s\n", class_name, player->client->pers.netname);

		if (player->client->keyindex)
			qb_hasflag = qtrue;
	}// while

	if (stats[0])
	{
		// alarm1
		if (self == self->parent->alarm1)
		{
			if (qb_hasflag)
			{
				if (self->parent->flagalarm1name[0])
					Com_sprintf(alarmname, sizeof(alarmname), "%s", self->parent->flagalarm1name);
				else
					Com_sprintf(alarmname, sizeof(alarmname), "%s", "Flag Alarm #1");
			}
			else
			{
				if (self->parent->alarm1name[0])
					Com_sprintf(alarmname, sizeof(alarmname), "%s", self->parent->alarm1name);
				else
					Com_sprintf(alarmname, sizeof(alarmname), "%s", "Alarm #1");
			}
		}// end - alarm1

		// alarm2
		if (self == self->parent->alarm2)
		{
			if (qb_hasflag)
			{
				if (self->parent->flagalarm2name[0])
					Com_sprintf(alarmname, sizeof(alarmname), "%s", self->parent->flagalarm2name);
				else
					Com_sprintf(alarmname, sizeof(alarmname), "%s", "Flag Alarm #2");
			}
			else
			{
				if (self->parent->alarm2name[0])
					Com_sprintf(alarmname, sizeof(alarmname), "%s", self->parent->alarm2name);
				else
					Com_sprintf(alarmname, sizeof(alarmname), "%s", "Alarm #2");
			}
		}// end - alarm2

		// alarm3
		if (self == self->parent->alarm3)
		{
			if (qb_hasflag)
			{
				if (self->parent->flagalarm3name[0])
					Com_sprintf(alarmname, sizeof(alarmname), "%s", self->parent->flagalarm3name);
				else
					Com_sprintf(alarmname, sizeof(alarmname), "%s", "Flag Alarm #3");
			}
			else
			{
				if (self->parent->alarm3name[0])
					Com_sprintf(alarmname, sizeof(alarmname), "%s", self->parent->alarm3name);
				else
					Com_sprintf(alarmname, sizeof(alarmname), "%s", "Alarm #3");
			}
		}// end - alarm3
		
		Com_sprintf(message, sizeof(message), "%s\nalarm was tripped by:\n%s\n", alarmname, stats);

		if (Q_stricmp (message, self->parent->alarmmsg) != 0)
		{
			Com_sprintf(self->parent->alarmmsg, sizeof(self->parent->alarmmsg), "%s", message);
			self->parent->alarmclear = level.time + 5000;
			//tbp: don't clog the screen
			//BOTS_cp_console(self->parent->s.clientNum, message);
			trap_SendServerCommand( self->parent->s.clientNum, va("bots_table \"[!] %s\"", message));
		}
		else
		{
			if (level.time > self->parent->alarmclear)
			{
				//BOTS_cp_console(self->parent->s.clientNum, message);
				trap_SendServerCommand( self->parent->s.clientNum, va("bots_table \"[!] %s\"", message));
				self->parent->alarmclear = level.time + 5000;
			}
			else
			{
				self->nextthink = level.time + FRAMETIME;
				return;
			}
		}

		for (j = 0 ; j < level.maxclients ; j++)
		{
		  player = g_entities + j;
		  if (!player->inuse || !player->client || (player == self->parent))
			  continue; 
		  if (player->client->sess.sessionTeam == TEAM_SPECTATOR)
			  continue;
		  if (!OnSameTeam (self->parent, player))
			  continue; 
		  if (player->bots_class == BOTS_CAPTAIN) {
			  //tbp: don't clog the screen
			  //BOTS_cp_console(player->s.clientNum, message);
			  trap_SendServerCommand( player->s.clientNum, va("bots_table \"[!] %s\"", message));
			  Com_sprintf(player->alarmmsg, sizeof(player->alarmmsg), "%s", message);
		  }
		}
	}// end - if(j)
	self->nextthink = level.time + FRAMETIME;
}

/////////////////////////////////////////////
//
// BOTS_alarmname
//
// Routine called to name alarms - flag or regular
//
void BOTS_alarmname(int clientNum, char* cmd, qboolean qb_flagalarm)
{
	gentity_t* ent = g_entities + clientNum;
	char	alarmname[30];
	char	tmp[30];
	int		i;

	// stupid trap_Args doesn't work in the game code - only cgame
	alarmname[0] = 0;
	for (i = 1; i < trap_Argc(); i++)
	{
		trap_Argv(i, tmp, 30);
		strcat(alarmname, tmp);
		strcat(alarmname, " ");
	}

	alarmname[29] = 0;
	if (qb_flagalarm)
	{
		if (Q_stricmp (cmd, "flagalarm1") == 0)
			Com_sprintf(ent->flagalarm1name, sizeof(ent->flagalarm1name), "%s", alarmname);
		else if (Q_stricmp (cmd, "flagalarm2") == 0)
			Com_sprintf(ent->flagalarm2name, sizeof(ent->flagalarm2name), "%s", alarmname);
		else if (Q_stricmp (cmd, "flagalarm3") == 0)
			Com_sprintf(ent->flagalarm3name, sizeof(ent->flagalarm3name), "%s", alarmname);

		BOTS_cp_console(ent->s.clientNum, "Flag alarm has been named.");
	}
	else
	{
		if (Q_stricmp (cmd, "alarm1") == 0)
			Com_sprintf(ent->alarm1name, sizeof(ent->alarm1name), "%s", alarmname);
		else if (Q_stricmp (cmd, "alarm2") == 0)
			Com_sprintf(ent->alarm2name, sizeof(ent->alarm1name), "%s", alarmname);
		else if (Q_stricmp (cmd, "alarm3") == 0)
			Com_sprintf(ent->alarm3name, sizeof(ent->alarm1name), "%s", alarmname);

		BOTS_cp_console(ent->s.clientNum, "Alarm has been named.");
	}

}

/////////////////////////////////////////////
//
// BOTS_alarm
//
// Routine called to place an alarm
//
void BOTS_alarm(int clientNum)
{
	vec3_t		wallp;
	vec3_t		forward;
	vec3_t		muzzle;
	trace_t		tr;
	qboolean	qb_alarm2 = qfalse;
	qboolean	qb_alarm3 = qfalse;

	gentity_t*	ent = g_entities + clientNum;
	gentity_t*	alarm;

	// check to see if this class can set an alarm
	if (ent->bots_class != BOTS_SNIPER)
	{
		BOTS_cp_console( clientNum, "Only the Sniper can place an alarm");
		return;
	}

	// check ammo
	if (ent->client->ps.ammo[WP_BFG] < 50)
	{
		BOTS_cp_console( clientNum, "You need at least 50 cells to place an alarm.");
		return;
	}

	// figure out what alarm we want to make
	if (ent->alarm1)
	{
		qb_alarm2 = qtrue;
		if (ent->alarm2)
			qb_alarm3 = qtrue;
	}

	// too many alarms
	if (qb_alarm3 && ent->alarm3)	
	{
		BOTS_cp_console( clientNum, "You have already set 3 alarms.");
		return;
	}

	// set aiming directions
	AngleVectors (ent->client->ps.viewangles, forward, NULL, NULL);

	// setup muzzle point
	CalcMuzzlePoint(ent, forward, NULL, NULL, muzzle);

	// Setup end point of the vector we want to trace
	VectorCopy(muzzle, wallp);
	VectorMA(wallp, 50, forward, wallp);

	// do the trace
	trap_Trace(&tr, muzzle, NULL, NULL, wallp, ent->s.number, MASK_SOLID);

	// Line complete ? (ie. no collision)
	if (tr.fraction == 1.0)
	{
		BOTS_cp_console( clientNum, "Too far from wall.");
		return;
	}

	// save net bandwidth
	SnapVector( tr.endpos );

	// wall found
	BOTS_cp_console( clientNum, "Alarm attached.");

	// cell usage
	ent->client->ps.ammo[WP_BFG] -= 50;

	//
	// Attatch a model for the alarm mount
	//
	alarm					= G_Spawn();
	alarm->s.eType			= ET_GENERAL;
	alarm->s.modelindex		= G_ModelIndex("models/bots/alarm.md3");
	alarm->s.modelindex2	= 0; // This is non-zero if it's a dropped item
	alarm->s.number			= alarm - g_entities;
	alarm->classname		= "alarm";
	alarm->r.contents		= 0;//CONTENTS_SOLID;
	alarm->clipmask			= 0;//CONTENTS_SOLID;
	alarm->parent			= ent;
	alarm->think			= BOTS_alarm_think;
	alarm->nextthink		= level.time + FRAMETIME;

	alarm->s.weapon			= WP_ROCKET_LAUNCHER;


	// Set orgin of laser mount to point of contact with wall
	G_SetOrigin(alarm, tr.endpos);

	// rotate the alarm so it lays nicely on the surface
	VectorNegate(tr.plane.normal, tr.plane.normal);
	vectoangles(tr.plane.normal, alarm->s.apos.trBase);

	trap_LinkEntity (alarm);

	// assign the entity to the player
	if (qb_alarm3)
		ent->alarm3 = alarm;
	else if (qb_alarm2)
		ent->alarm2 = alarm;
	else
		ent->alarm1 = alarm;
}

/////////////////////////////////////////////
//
// BOTS_alarmkill
//
// Routine to remove all alarms
//
void BOTS_alarmkill(int clientNum, qboolean ab_nomsg)
{
	gentity_t* ent = g_entities + clientNum;
	qboolean qb_hadalarm = qfalse;

	if (!ent->client)
		return;

	if (ent->alarm1)
	{
		BOTS_laserbase_explode(ent->alarm1);
		//G_FreeEntity(ent->alarm1);
		qb_hadalarm = qtrue;
	}
	if (ent->alarm2)
	{
		BOTS_laserbase_explode(ent->alarm2);
		//G_FreeEntity(ent->alarm2);
		qb_hadalarm = qtrue;
	}
	if (ent->alarm3)
	{
		BOTS_laserbase_explode(ent->alarm3);
		//G_FreeEntity(ent->alarm3);
		qb_hadalarm = qtrue;
	}

	ent->alarm1 = NULL;
	ent->alarm2 = NULL;
	ent->alarm3 = NULL;

	ent->alarm1name[0]		= 0;
	ent->alarm2name[0]		= 0;
	ent->alarm3name[0]		= 0;
	ent->flagalarm1name[0]	= 0;
	ent->flagalarm2name[0]	= 0;
	ent->flagalarm3name[0]	= 0;
	ent->alarmmsg[0]		= 0;

	if (ent->health <= 0)
		return;

	if (!ab_nomsg) {
	if (qb_hadalarm == qtrue)
		BOTS_cp_console( clientNum, "All alarms have been removed.");
	else
		BOTS_cp_console( clientNum, "You have to place an alarm first.");
	}
}

/////////////////////////////////////////////
//
// BOTS_cloakwalk
//
// Toggles the use of cloakwalk.  This will enable a level 4 sniper to
// move (very slowly) while cloaked.
//
void BOTS_cloakwalk(int clientNum)
{
	gentity_t* ent = g_entities + clientNum;

	if (!ent->client)
		return;

	// check to see if this class can use cloakwalk
	if (ent->bots_class != BOTS_SNIPER)
	{
		BOTS_cp_console(clientNum, "Only the Sniper can use Cloak Walk.");
		return;
	}

	if (ent->client->pers.classlevel < 4)
	{
		BOTS_cp_console(clientNum, "You must be level 4 to use cloak walk.");
		return;
	}

	if(ent->client->qb_cloakWalk)
	{
		ent->client->qb_cloakWalk = qfalse;
		//BOTS_cp_console(clientNum, "Cloak Walk disabled.");
	}
	else
	{
		ent->client->qb_cloakWalk = qtrue;
		//BOTS_cp_console(clientNum, "Cloak Walk enabled.");

		if (ent->client->invisible)
			trap_SendServerCommand(ent->s.clientNum, "walk_client");
	}

	// write client data to the config string
	//csfix: BOTS_SniperConfigString(clientNum);
}

void BOTS_sniper_cloak(int clientNum, qboolean turn_on)
{
	gentity_t* ent = g_entities + clientNum;

	if (!ent->client)
		return;

	if (turn_on)
	{
		ent->client->invisible = qtrue;
		//BOTS_cp_console(ent->s.clientNum, "You are now cloaked.");
		//ent->r.svFlags |= SVF_NOCLIENT;
		ent->client->ps.powerups[PW_INVIS] = INT_MAX;

		if (ent->client->qb_cloakWalk)
			trap_SendServerCommand(ent->s.clientNum, "walk_client");

	}
	else
	{
		ent->client->invisible = qfalse;
		//BOTS_cp_console(clientNum, "You are visible again.");
		//ent->r.svFlags &= ~SVF_NOCLIENT;
		ent->client->ps.powerups[PW_INVIS] = 0;
		bots_SetSpeed(ent);
	}

	// write client data to the config string
	//csfix: BOTS_SniperConfigString(ent->s.clientNum);
}

void BOTS_cloak(int clientNum)
{
	gentity_t* ent = g_entities + clientNum;
	int cell_amount = 1000;

	if (!ent->client)
		return;

	if (ent->bots_class != BOTS_SNIPER)
	{
		BOTS_cp_console(clientNum, "Only the Sniper can cloak.");
		return;
	}

	cell_amount = ent->client->pers.maxammo[WP_BFG] * .75;

	if (ent->client->pers.classlevel > 1)
		cell_amount -= (ent->client->pers.classlevel - 1) * 10;

	// check ammo
	if (ent->client->ps.ammo[WP_BFG] < cell_amount)
	{
		BOTS_cp_console( clientNum, va("You need at least %i cells to cloak.", cell_amount));
		return;
	}
	ent->client->ps.ammo[WP_BFG] -= cell_amount;

	BOTS_sniper_cloak(ent->s.clientNum, qtrue);
}

/////////////////////////////////////////////
//
// BOTS_SniperCommands
//
// This will parse the passed cmd string and call to the correct functions.
// 
// RETURNS
//    qfalse : cmd is not a sniper command
//	  qtrue	 : cmd is a sniper command
//
qboolean BOTS_SniperCommands(int clientNum, char* cmd)
{
	//gentity_t* ent = g_entities + clientNum;

	if (Q_stricmp (cmd, "alarm") == 0 )
	{
		BOTS_alarm(clientNum);
		return qtrue;
	}
	if (Q_stricmp (cmd, "alarmkill") == 0 )
	{
		BOTS_alarmkill(clientNum, qfalse);
		return qtrue;
	}
	if (Q_stricmp (cmd, "sight") == 0 )
	{
		BOTS_sight(clientNum);
		return qtrue;
	}
	if (Q_stricmp (cmd, "cloakwalk") == 0 )
	{
		BOTS_cloakwalk(clientNum);
		return qtrue;
	}
//	if (Q_stricmp (cmd, "cloak") == 0 )
//	{
//		BOTS_cloak(clientNum);
//		return qtrue;
//	}
	if (Q_stricmp (cmd, "alarm1") == 0 ||
		Q_stricmp (cmd, "alarm2") == 0 ||
		Q_stricmp (cmd, "alarm3") == 0)
	{
		BOTS_alarmname(clientNum, cmd, qfalse);
		return qtrue;
	}
	if (Q_stricmp (cmd, "flagalarm1") == 0 ||
		Q_stricmp (cmd, "flagalarm2") == 0 ||
		Q_stricmp (cmd, "flagalarm3") == 0)
	{
		BOTS_alarmname(clientNum, cmd, qtrue);
		return qtrue;
	}
	if (Q_stricmp (cmd, "flare") == 0)
	{
		BOTS_fire_flare(clientNum);
		return qtrue;
	}

	return qfalse;
}

/////////////////////////////////////////////
//
// BOTS_ClientThink_sniper
//
// Sniper think routine.  Called once for each client think if
// the client is a sniper.
//
void BOTS_ClientThink_sniper(gentity_t* ent, usercmd_t *ucmd)
{
	int cloaktime = 10000;

	// dead - clean it up
	if (ent->health <= 0)
		return;
	else
	{
		qboolean	dataChanged = qfalse;
		if (!ucmd)
			return;

		// client moved and/or is being transported
		if ( ucmd->forwardmove != 0 || ucmd->rightmove   != 0 ||
			 ucmd->upmove      != 0 || ent->client->keyindex > 0 ||
			 ent->client->trooptransport)
		{
			VectorCopy (ent->s.pos.trBase, ent->client->last_position);
			bots_updateCloaking(ent,-1);
			ent->client->last_movement = level.time;

			if (ent->client->invisible)
			{
				if(!ent->client->qb_cloakWalk)
				{
					ent->client->invisible = qfalse;
					dataChanged = qtrue;
				}
			}
		}

		// client is standing still
		else
		{
			int timeleft;


			// Cloaktime has been nudged to avoid rounding sideeffects on the HUD
			switch (ent->client->pers.classlevel) {
			case 1:
				cloaktime = 7999;
				break;
			case 2:
				cloaktime = 4999;
				break;
			case 3:
				cloaktime = 1999;
				break;
			case 4:
				cloaktime = 999;
				break;
			default:
				cloaktime = 9999;
			}
			timeleft = (ent->client->last_movement + cloaktime) - level.time;

			// enough time has passed since our last movement, can we cloak?
			if (timeleft <= 0)
			{
				if (!ent->client->invisible)
				{
					// cast 4 vectors to make it work better
					vec3_t		forward, right, wallp, muzzle;	
					trace_t		tr1, tr2, tr3, tr4;

					// set aiming directions
					AngleVectors (ent->client->ps.viewangles, forward, right, NULL);

					// setup muzzle point
					CalcMuzzlePoint(ent, forward, NULL, NULL, muzzle);

					// Setup end point of the vector we want to trace
					VectorCopy(muzzle, wallp);
					VectorMA(wallp, 50, forward, wallp);
					trap_Trace(&tr1, muzzle, NULL, NULL, wallp, ent->s.number, MASK_SOLID);
					SnapVector( tr1.endpos );

					// Setup end point of the vector we want to trace
					VectorCopy(muzzle, wallp);
					VectorMA(wallp, -50, forward, wallp);
					trap_Trace(&tr2, muzzle, NULL, NULL, wallp, ent->s.number, MASK_SOLID);
					SnapVector( tr2.endpos );

					// Setup end point of the vector we want to trace
					VectorCopy(muzzle, wallp);
					VectorMA(wallp, 50, right, wallp);
					trap_Trace(&tr3, muzzle, NULL, NULL, wallp, ent->s.number, MASK_SOLID);
					SnapVector( tr3.endpos );

					// Setup end point of the vector we want to trace
					VectorCopy(muzzle, wallp);
					VectorMA(wallp, -50, right, wallp);
					trap_Trace(&tr4, muzzle, NULL, NULL, wallp, ent->s.number, MASK_SOLID);
					SnapVector( tr4.endpos );

					// Line complete ? (ie. no collision)	
					if (tr1.fraction == 1.0 && tr2.fraction == 1.0 && tr3.fraction == 1.0 && tr4.fraction == 1.0)
					{
						ent->client->last_movement = level.time;
						// too far from a wall
					}	
					else
					{
						ent->client->invisible = qtrue;
						dataChanged = qtrue;
					}
				}// check wall distance
			}
			else {
				bots_updateCloaking(ent, (short) (timeleft/1000));
			}
		}

		if (ent->client->invisible && ent->client->qb_cloakWalk)
		{
			ent->client->ps.speed *= .2;
		}
		
		if (dataChanged)
		{
			if (ent->client->invisible)
			{
				BOTS_sniper_cloak(ent->s.clientNum, qtrue);
			}
			else
			{
				ent->client->last_movement = level.time;
				BOTS_sniper_cloak(ent->s.clientNum, qfalse);
			}
		}
	} // health > 0
}
